package com.panda.tool.listener;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.RowTypeEnum;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ConverterUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.util.StringUtils;
import com.alibaba.fastjson2.JSON;
import com.panda.tool.config.DDLFileConfigProperties;
import com.panda.tool.dto.ExcelFieldDTO;
import com.panda.tool.dto.ExcelTableAttrDTO;
import com.panda.tool.dto.ExcelTableDTO;
import com.panda.tool.service.ToolService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;

/**
 * @ClassName ExcelToDDLListener
 * @Description EXCEL转DDL监听器
 * @Author LD
 * @Date 2023-11-17 12:42:03
 * @Version 1.0
 */
@Slf4j
public class ExcelToDDLListener implements ReadListener<ExcelFieldDTO> {

    /**
     * 设置最大1000条
     */
    private static final int BATCH_COUNT = 1000;

    /**
     * 静态变量：是
     */
    private static final String YES = "是";

    /**
     * 缓存的数据
     */
    private List<ExcelFieldDTO> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    /**
     * 缓存表属性数据
     */
    private ExcelTableAttrDTO tableAttr = new ExcelTableAttrDTO();


    private AtomicBoolean tableAttrFlag = new AtomicBoolean(true);

    private DDLFileConfigProperties fileConfigProperties;

    public ExcelToDDLListener(DDLFileConfigProperties fileConfigProperties) {
        this.fileConfigProperties = fileConfigProperties;
    }


    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        if(tableAttrFlag.compareAndSet(true,false)){
            log.info("解析到excel表头数据>>>>>>>>>>>>>>>>:{}", JSON.toJSONString(headMap));
            Map<Integer, String> tableAttrMap = ConverterUtils.convertToStringMap(headMap, context);
            String tableAttrs[] = tableAttrMap.get(0).split("-");
            // 表头信息设置
            tableAttr = ExcelTableAttrDTO.builder().tableName(tableAttrs[0]).tableCode(tableAttrs[1]).build();
        }
    }

    @Override
    public void invoke(ExcelFieldDTO data, AnalysisContext context) {
        log.info("解析excel表内容数据>>>>>>>>>>>>>>>>:{}", JSON.toJSONString(data));
        // 如果当前行Excel数据均为空值，则不装载该行数据
        if(isLineNullValue(data)){
            return;
        }
        log.info("将解析的excel表内容数据保存到集合中缓存>>>>>>>>>>>>>>>>:{}", JSON.toJSONString(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了，抛出异常
        if (cachedDataList.size() >= BATCH_COUNT) {
            throw new RuntimeException("已达到最大容量，请手动分批后再进行处理");
        }
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        log.info("excel表所有数据解析完成，开始生成DDL脚本文件...");
        this.createFileToLocal();
    }

    /**
     * 判断整行单元格数据是否均为空
     */
    private boolean isLineNullValue(ExcelFieldDTO data) {
        try {
            List<Field> fields = Arrays.stream(data.getClass().getDeclaredFields())
                    .filter(f -> f.isAnnotationPresent(ExcelProperty.class))
                    .collect(Collectors.toList());
            List<Boolean> lineNullList = new ArrayList<>(fields.size());
            for (Field field : fields) {
                field.setAccessible(true);
                Object value = field.get(data);
                if (ObjectUtil.isNull(value)) {
                    lineNullList.add(Boolean.TRUE);
                } else {
                    lineNullList.add(Boolean.FALSE);
                }
            }
            return lineNullList.stream().allMatch(Boolean.TRUE::equals);
        } catch (Exception e) {
            log.error("解析excel数据行[{}]失败，失败信息: {}", data, e.getMessage());
        }
        return true;
    }

    /**
     * 生成DDL语句
     * @return
     */
    public String createDDL(){
        log.info("开始进行DDL脚本拼接...");
        // 前置脚本
        String preSql = "\n CREATE TABLE `"+tableAttr.getTableCode()+"` ( \n";
        // 后置脚本
        String postSql = ") " +
                "ENGINE="+fileConfigProperties.getEngine()+" " +
                "DEFAULT CHARSET="+fileConfigProperties.getCharset()+" " +
                "COLLATE="+fileConfigProperties.getCollate()+" " +
                "COMMENT='"+tableAttr.getTableName()+"' ;";

        // 脚本拼接
        StringBuffer bufferSql = new StringBuffer();
        bufferSql.append(preSql);

        // 拼接字段
        cachedDataList.forEach(e->{
            // 字段编码
            bufferSql.append("`"+e.getFieldCode()+"` ");
            // 字段类型
            bufferSql.append(e.getFieldType()+" ");
            // 字段是否为空
            if(YES.equals( e.getMustFlag())){
                bufferSql.append(" NOT NULL ");
            }
            // 字段默认值
            if(StringUtils.isNotBlank(e.getDefaultVal())){
                bufferSql.append(" DEFAULT '"+e.getDefaultVal()+"' ");
            }
            // 字段注释
            bufferSql.append(" COMMENT '" +e.getFieldName()+"' ");
            // 逗号结束，换行
            bufferSql.append(", \n");
        });

        // 拼接主键
        cachedDataList.forEach(e->{
            if(YES.equals(e.getPkFlag())){
                bufferSql.append(" PRIMARY KEY (`"+e.getFieldCode()+"`)  \n");
            }
        });

        // 判断最后一个字符是不是逗号,如果是则去掉
        String preSqlStr = bufferSql.toString();
        // 判断最后一个字符是不是逗号,如果是则去掉
        if(",".equals(preSqlStr.charAt(preSqlStr.length()-1))){
            preSqlStr = preSqlStr.substring(0,preSqlStr.length()-1);
        }

        // 形成最终的脚本
        preSqlStr = preSqlStr + postSql;

        log.info("DDL脚本拼接结束，脚本为:{}",preSqlStr);
        return preSqlStr;
    }

    /**
     * 获取excel后，拼接成DDL
     * 拼接成DDL后，生成文件到本地
     */
    public void createFileToLocal(){
        // ddl文件保存地址
        String path = fileConfigProperties.getPath();
        log.info("ddl文件保存地址为>>>>>>>>>>>>>>>>:{}", path);
        // DDL文件全路径
        path = path + tableAttr.getTableCode() + "_DDL.sql";
        // 写文件
        FileUtil.writeUtf8String(this.createDDL(), path);
        log.info("ddl文件生成成功，文件保存在>>>>>>>>>>>>>>>>:{}",path);
    }

}
